Improve resilience to delayed shared file system visibility - #7489
Open
pditommaso wants to merge 3 commits into
Open
Improve resilience to delayed shared file system visibility#7489pditommaso wants to merge 3 commits into
pditommaso wants to merge 3 commits into
Conversation
`FileHelper.isPathSharedFS` only matched `nfs` and `lustre`, so every delayed-visibility mitigation gated on `workDirIsSharedFS` was silently disabled on CephFS (and BeeGFS/GPFS) work directories. Extract the type classification into `isSharedFsType` so it can be tested without depending on the file system the tests run on. Ref #7247 Assisted-by: Claude Code (Opus 5) Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
The `.exitcode` file is the completion marker polled by the executors, but `on_exit` wrote it *before* running the optional `sync` command. As a result `NXF_ENABLE_FS_SYNC=true` provided no write ordering guarantee: on a shared file system with delayed write-back the driver could observe the marker (or, for API driven executors such as K8s, the terminated state that follows it) while the task outputs were still missing or truncated. Move the sync command ahead of the exit file write so that everything the task wrote into the work directory is flushed before completion is published. Ref #7247 Assisted-by: Claude Code (Opus 5) Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
The K8s executor detects task completion from the pod terminated state
returned by the API, then immediately reads the task work directory. On a
shared file system with delayed write-back the driver can observe the pod as
terminated while the task writes are not yet visible on its own mount, which
surfaces as an empty `.exitcode` ("terminated for an unknown reason"), a
truncated `.command.env` ("Missing environment variable: nxf_out_eval_N") or
missing output files.
Defer the task completion until the `.exitcode` file is visible and non
empty, up to `executor.exitReadTimeout`, mirroring the logic already used by
the grid executors. Since the exit file is the last file written by the task
wrapper, it acts as a barrier for the whole work directory.
The wait is skipped when the work dir is not a shared file system, when
Fusion is enabled, and when the API reports a non-zero exit code -- a task
killed by the system (e.g. OOMKilled) never writes the exit file, so waiting
for it would only delay the error report.
Ref #7247
Assisted-by: Claude Code (Opus 5)
Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
✅ Deploy Preview for nextflow-docs canceled.
|
Contributor
|
Tested as working e2e 4x in a row on our infra on my minimal reproduction pipeline! |
Contributor
|
Very excited to have this rolled into a release! :) it's been a massive pain on our infra... |
Member
Author
|
Thanks for validating this PR end to end |
pditommaso
force-pushed
the
master
branch
2 times, most recently
from
August 20, 2026 12:03
5f935c2 to
d1eae20
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Surgical fix for #7247 — stochastic "null/truncated/empty file" failures on K8s + CephFS.
Analysis
The two logs attached to the issue show two symptoms of one mechanism.
nextflow.2.log(25.10.5, nf-k8s 1.2.2) —SAMTOOLS_SORT (WT_REP1):The pod reported exit code
0, but 25.10.5 evaluatedtask.exitStatus = k8sExitCode ?: readExitFile(), and0is falsy in Groovy — so a successful task always fell back to reading.exitcode, which came back empty. This particular symptom is already fixed on master by 454a2ae (#6484,v25.11.0-edge), which is why the reporter only observes it on v25.nextflow.3.log(26.04.1, nf-k8s 1.5.2) —STAR_GENOMEGENERATE:41 ms after the API reported the pod terminated,
.command.envparsed cleanly but was missing its last entry. That file is written by a sequence ofecho … >> .command.envappends, so a short read loses the trailing keys.Root cause. Nextflow decides a K8s task is finished from an out-of-band signal (the pod terminated state), then immediately reads the work directory over a different mount of the shared filesystem. Nothing forces the task's writes to be visible on the driver first. Two code-level facts make it worse:
FileHelper.isPathSharedFS()only matchednfsandlustre, so on CephFSworkDirIsSharedFSwasfalseand every existing lag mitigation was disabled.on_exitwrote.exitcodebefore running{{sync_cmd}}, so evenNXF_ENABLE_FS_SYNC=truegave no write-ordering guarantee.Ironically #6484 also removed the accidental read barrier: in v25 the driver at least touched the filesystem before finalizing. In v26 it goes straight from API status to
.command.env, so the symptom moved rather than disappeared.Changes
FileHelper— recogniseceph,beegfsandgpfsas shared filesystems. Classification extracted intoisSharedFsType(String)so it can be tested independently of the filesystem the tests run on.command-run.txt— move{{sync_cmd}}ahead of the.exitcodewrite, so the completion marker is only published after everything else the task wrote has been flushed.sync_cmdremains opt-in viaNXF_ENABLE_FS_SYNC.K8sTaskHandler— defer task completion until.exitcodeis visible and non-empty, up toexecutor.exitReadTimeout(default 270 s), mirroringGridTaskHandler.readExitStatus(). Because the exit file is the last file the wrapper writes, it acts as a barrier for the whole work directory — covering.command.env,.command.outand the output globs, not just the exit status.The wait is skipped when the work dir is not a shared FS, when Fusion is enabled, and when the API reports a non-zero exit code: a task killed by the system (e.g. OOMKilled) never writes the exit file, so waiting would only delay the error report by 270 s.
Relation to #7281
#7281 addresses the same issue across 25 files (~1000 lines). This is the minimal subset. It deliberately omits the per-file retry layer (
TaskFileRetryaround.command.env/.command.out): a retry can detect a missing file, but a truncated.command.outor short output file is indistinguishable from a correct one. Only a barrier on the last-written marker generalises to all downstream reads.It also keeps
sync_cmdopt-in rather than defaulting it on for shared filesystems —syncon a busy HPC node flushes every client's dirty data, and change 3 carries the reliability on its own.Trade-offs
.exitcodeon a shared FS now costs up toexitReadTimeoutper task instead of failing fast. Bounded, and the non-zero-exit-code carve-out keeps the common failure paths fast..exitcodeitself unflushed. That is what the bounded wait in change 3 (and the pre-existing grid polling) is for.Tests
Scoped to the three modified classes:
FileHelperTest—should detect shared file system typeBashWrapperBuilderTest—should flush the file system before writing the exit file, plus the two rendered-wrapper golden filesK8sTaskHandlerTest— 4 new specs covering the barrier, the timeout, the non-shared-FS bypass and the failed-pod bypassLocally: 245 tests, 0 failures across the three classes. Full suite left to CI.
Neither I nor CI can reproduce the race itself — only @BioWilko's cluster can confirm the end-to-end fix.